package opensearchext

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	opensearchapi "github.com/opensearch-project/opensearch-go/opensearchapi"
)

// ----- API Definition -------------------------------------------------------

type Action struct {
	ActionName string
	IndexName  string
	Alias      string
}

type Alias struct {
	Actions []Action `json:"actions,omitempty"`

	IfPrimaryTerm       *int
	IfSeqNo             *int
	Refresh             string
	Routing             string
	Timeout             time.Duration
	Version             *int
	VersionType         string
	WaitForActiveShards string

	Pretty     bool
	Human      bool
	ErrorTrace bool
	FilterPath []string

	Header http.Header

	ctx context.Context
}

func (a Action) MarshalJSON() ([]byte, error) {
	actMap := make(map[string]interface{})
	indexAlias := map[string]string{"index": a.IndexName, "alias": a.Alias}
	actMap[a.ActionName] = indexAlias

	return json.Marshal(actMap)
}

func (al Alias) MarshalJSON() ([]byte, error) {
	alMap := make(map[string]interface{})
	if len(al.Actions) > 0 {
		alMap["actions"] = al.Actions
	}

	return json.Marshal(alMap)
}

func (a Alias) Do(ctx context.Context, transport opensearchapi.Transport) (*opensearchapi.Response, error) {
	var (
		method string
		path   strings.Builder
		params map[string]string
	)

	method = "POST"
	//_aliases
	path.Grow(len("/_aliases"))
	path.WriteString("/_aliases")

	params = make(map[string]string)

	if a.IfPrimaryTerm != nil {
		params["if_primary_term"] = strconv.FormatInt(int64(*a.IfPrimaryTerm), 10)
	}

	if a.IfSeqNo != nil {
		params["if_seq_no"] = strconv.FormatInt(int64(*a.IfSeqNo), 10)
	}

	if a.Refresh != "" {
		params["refresh"] = a.Refresh
	}

	if a.Routing != "" {
		params["routing"] = a.Routing
	}

	if a.Timeout != 0 {
		params["timeout"] = formatDuration(a.Timeout)
	}

	if a.Version != nil {
		params["version"] = strconv.FormatInt(int64(*a.Version), 10)
	}

	if a.VersionType != "" {
		params["version_type"] = a.VersionType
	}

	if a.WaitForActiveShards != "" {
		params["wait_for_active_shards"] = a.WaitForActiveShards
	}

	if a.Pretty {
		params["pretty"] = "true"
	}

	if a.Human {
		params["human"] = "true"
	}

	if a.ErrorTrace {
		params["error_trace"] = "true"
	}

	if len(a.FilterPath) > 0 {
		params["filter_path"] = strings.Join(a.FilterPath, ",")
	}

	reqBody, err := json.Marshal(a)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal structure: %s", err)
	}
	body := strings.NewReader(string(reqBody))

	req, err := newRequest(method, path.String(), body)
	if err != nil {
		return nil, fmt.Errorf("failed to form request: %s", err)
	}

	if len(params) > 0 {
		q := req.URL.Query()
		for k, v := range params {
			q.Set(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}
	if body != nil {
		req.Header[headerContentType] = []string{headerContentTypeJSON}
	}

	if len(a.Header) > 0 {
		if len(req.Header) == 0 {
			req.Header = a.Header
		} else {
			for k, vv := range a.Header {
				for _, v := range vv {
					req.Header.Add(k, v)
				}
			}
		}
	}

	if ctx != nil {
		req = req.WithContext(ctx)
	}

	res, err := transport.Perform(req)
	if err != nil {
		return nil, fmt.Errorf("failed to perform request: %s", err)
	}

	response := opensearchapi.Response{
		StatusCode: res.StatusCode,
		Body:       res.Body,
		Header:     res.Header,
	}

	return &response, nil
}
